package com.hzz.web.app;

import com.alibaba.fastjson.JSONObject;
import com.hzz.api.log.ILogLoginService;
import com.hzz.api.user.IAuthRoleService;
import com.hzz.api.user.IRoleSourceService;
import com.hzz.api.user.IUserService;
import com.hzz.dto.log.LogLoginDto;
import com.hzz.dto.user.AuthPermDto;
import com.hzz.dto.user.AuthRoleDto;
import com.hzz.dto.user.UserDto;
import com.hzz.framework.Response;
import com.hzz.framework.annotation.NoAuthorize;
import com.hzz.framework.config.EncryptConfig;
import com.hzz.framework.security.CryptoJSDES;
import com.hzz.framework.security.MD5;
import com.hzz.framework.util.*;
import com.hzz.framework.web.controller.BaseController;
import io.jsonwebtoken.Claims;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.*;

/**
 * <p>登录登出action
 */
@Api("登录/登出")
@RestController("ApiLoginController")
@Slf4j
@NoAuthorize
public class LoginController extends BaseController {
    private static final String acPrefix = "/api/staff/sign/";
    private final String[] STAFF_TOKEN = new String[]{"id", "account", "areaId", "areaName", "avatar", "cellphone", "coreResponsibility", "email", "gender", "job", "jobNo", "level", "name", "orgId", "orgName", "riverId", "riverName", "superviseRiverReach", "type", "unitCellphone", "unitName", "isSuper", "telephone", "idCard", "address", "orgInfos", "staffOrgNode", "currAreaType", "currAreaId", "currAreaName", "authorizationInfoPerms", "authorizationInfoRoles", "xToken"};
    @Autowired
    EncryptConfig encryptConfig;
    @Autowired
    EhcacheUtil cache;
    @Autowired
    IRoleSourceService roleSourceService;
    @Autowired
    IUserService userService;
    @Autowired
    IAuthRoleService authRoleService;
    @Autowired
    ILogLoginService logLoginService;

    /**
     * <p>员工登录
     */
    @RequestMapping(method = {RequestMethod.POST}, value = acPrefix + "/login")
    @ApiOperation(value = "登录")
    public Response login(@RequestBody UserDto dto) {
        log.info("login");
        Response result = new Response(0, "success");
        try {
            String ip = IpUtil.getIpAddr(request);
            String account = dto.getAccount();
            String password = dto.getPassword();
            String vcerifyCode = dto.getVerifyCode();

            if (ValidatorUtil.isNullEmpty(account) || ValidatorUtil.isNullEmpty(password)) {
                return Response.error("员工名或密码不能为空!");
            }

//            if (ValidatorUtil.isNullEmpty(vcerifyCode)) {
//                return Response.error("验证码不能为空!");
//            }
//            String orginVerifyCode = (String) request.getSession().getAttribute(CommonConstant.LOGIN_VERIFY_CODE);
//            request.getSession().removeAttribute(CommonConstant.LOGIN_VERIFY_CODE);
//            if (!vcerifyCode.equalsIgnoreCase(orginVerifyCode)) {
//                return Response.error("请输入正确的验证码");
//            }

            String ipCacheKey = CommonConstant.JWT_IP_COUNT_PREFIX + DateUtil.getCurDateStr("yyyyMMddHH") + ":" + ip;
            String ipCountStr = "" + cache.get(ipCacheKey);
            if (ValidatorUtil.notEmpty(ipCountStr) && ValidatorUtil.isIntNumber(ipCountStr)) {
                Integer ipCount = Integer.valueOf(ipCountStr) + 1;
                cache.set(ipCacheKey, ipCount);
                if (ipCount >= 300) {
                    log.error("IP,【"+ip + "】登录太频繁!次数:" + ipCount);
                    return Response.error("登录太频繁,下一小时再试");
                }
            } else {
                cache.set(ipCacheKey, 1);
            }

            String uidCacheKey = CommonConstant.JWT_UID_COUNT_PREFIX + DateUtil.getCurDateStr("yyyyMMddHH") + ":" + account;
            String uidCountStr = "" + cache.get(uidCacheKey);
            if (ValidatorUtil.notEmpty(uidCountStr) && ValidatorUtil.isIntNumber(uidCountStr)) {
                Integer uidCount = Integer.valueOf(uidCountStr) + 1;
                cache.set(uidCacheKey, uidCount);
                if (uidCount >= 120) {
                    log.error("账号,【"+account + "】登录太频繁!次数:" + uidCount);
                    return Response.error("登录太频繁,下一小时再试");
                }
            } else {
                cache.set(uidCacheKey, 1);
            }

            String uidPwdErrCacheKey = CommonConstant.JWT_UID_PWD_ERR_PREFIX + DateUtil.getCurDateStr("yyyyMMddHH") + ":" + account;
            String uidPwdErrCountStr = "" + cache.get(uidPwdErrCacheKey);
            Integer uidPwdErrCount = 0;
            if (ValidatorUtil.notEmpty(uidPwdErrCountStr) && ValidatorUtil.isIntNumber(uidPwdErrCountStr)) {
                uidPwdErrCount = Integer.valueOf(uidPwdErrCountStr);
                if (uidPwdErrCount >= 60) {
                    log.error("账号,【"+account + "】登录失败次数达到最大值,下一小时再试!次数:" + uidPwdErrCount);
                    return Response.error("登录失败次数达到最大值,下一小时再试!");
                }
            }
            // 解密
            password = CryptoJSDES.desDecrypt(password, MD5.md5Hex(encryptConfig.getPRIKEY()));
            // 验证
            UserDto staffDto = roleSourceService.findUserByAccount(account);
            String pwdhex = MD5.passwordMd5Hex(password);
            if (staffDto == null || !(account.equals(staffDto.getAccount()) && pwdhex.equals(staffDto.getPassword()) && staffDto.getType() != CommonConstant.TYPE_超管 && staffDto.getType() != CommonConstant.TYPE_管理员)) {
                if (ValidatorUtil.notEmpty(uidPwdErrCountStr) && ValidatorUtil.isIntNumber(uidPwdErrCountStr)) {
                    uidPwdErrCount = Integer.valueOf(uidPwdErrCountStr) + 1;
                    cache.set(uidPwdErrCacheKey, uidPwdErrCount);
                    if (uidPwdErrCount >= 60) {
                        log.error("账号,【"+account + "】登录失败次数达到最大值,下一小时再试!次数:" + uidPwdErrCount);
                        return Response.error("登录失败次数达到最大值,下一小时再试!");
                    }
                } else {
                    cache.set(uidPwdErrCacheKey, 1);
                }
                log.error("账号,【"+account + "】登录失败,员工名或密码错误!次数:"+uidPwdErrCount);
                return Response.error("登录失败,员工名或密码错误!");
            }
            cache.set(uidPwdErrCacheKey, 0);
            UserDto staffXDto = userService.findDataById(staffDto);
            roleSourceService.lastLogin(staffXDto);

            String xToken = CommonConstant.JWT_HEADER_TOKEN_PREFIX_APP + MD5.md5Hex(staffXDto.getAccount() + IdUtil.createUUID(32) + IdUtil.createUUID(32));

            Map<String, Object> resultData = loginX(staffXDto, xToken);
            result.data = resultData;
            taskExecutor.execute(() -> {
                try {
                    //读取session中的员工
                    LogLoginDto logDto = new LogLoginDto();
                    logDto.setUserType(staffXDto.getType());//用户类型,0管理员,
                    logDto.setType(0);//类型0登录1登出
                    logDto.setIpAddr(IpUtil.getIpAddr(request));//请求的IP
                    logDto.setUserId(staffXDto.getId());//id
                    logDto.setUserName(staffXDto.getName());//员工名称
                    logDto.setDeviceMac(IpUtil.getMACAddress(logDto.getIpAddr()));//MAC地址
                    logDto.setIsSuper(staffXDto.getIsSuper());
                    logLoginService.saveOrUpdateData(logDto);
                } catch (Exception e) {
                }
            });
        } catch (SecurityException e) {
            request.getSession().removeAttribute(CommonConstant.LOGIN_VERIFY_CODE);
            result = Response.error("登录失败,员工名或密码错误!");
        } catch (Throwable ex) {
            request.getSession().removeAttribute(CommonConstant.LOGIN_VERIFY_CODE);
            log.error("登录失败,原因未知", ex);
            result = Response.error("登录失败,服务器异常!" + ex.getMessage());
        }
        return result;
    }

    /**
     * <p>员工登出
     */
    @RequestMapping(method = RequestMethod.GET, value = acPrefix + "logout")
    @ApiOperation(value = "登出")
    public Response logout() {
        log.info("logout.........");
        Response result = new Response(0, "success");
        try {
            log.debug("准备退出!");
            String authorization = request.getHeader(CommonConstant.JWT_HEADER_TOKEN_KEY);
            if (StringUtils.isNotEmpty(authorization)) {
                taskExecutor.execute(() -> {
                    Claims claims = JwtUtil.parseJWT(authorization);
                    String json = claims.getSubject();
                    UserDto staffDto = JSONObject.parseObject(json, UserDto.class);
//                    try {
//                        String xToken = staffDto.getXToken();
//                        try {
//                            //查询APP员工表
//                            String cacheXToken = CommonConstant.JWT_REDIS_TOKEN_KEY + xToken;
//                            String cacheUToken = "u:" + staffDto.getId() + ":";
//                            cache.del(cacheXToken);
//                            cache.del(cacheXToken + ":data");
//                            cache.del(cacheUToken);
//                        } catch (Exception e) {
//                            log.error("登出清除redis异常!", e);
//                        }
//                    } catch (Exception e) {
//                    }
                    try {
                        LogLoginDto logDto = new LogLoginDto();
                        logDto.setUserType(staffDto.getType());//用户类型,0管理员,
                        logDto.setType(1);//类型0登录1登出
                        logDto.setIpAddr(IpUtil.getIpAddr(request));//请求的IP
                        logDto.setStaffId(staffDto.getId());//id
                        logDto.setStaffName(staffDto.getName());//员工名称
                        logDto.setUserId(logDto.getStaffId());//id
                        logDto.setUserName(logDto.getStaffName());//员工名称
                        logDto.setDeviceMac(IpUtil.getMACAddress(logDto.getIpAddr()));//MAC地址
                        logDto.setIsSuper(staffDto.getIsSuper());
                        logLoginService.saveOrUpdateData(logDto);
                    } catch (Exception e) {
                    }
                });
            }
            try {
//                log.debug("========================================"+request.getAttribute(CommonConstant.JWT_HEADER_TOKEN_PREFIX + CommonConstant.JWT_HEADER_TOKEN_KEY));
                request.getSession().invalidate();
                cache.set(CommonConstant.JWT_CACHE_TOKEN_KEY_APP + request.getAttribute(CommonConstant.JWT_HEADER_TOKEN_PREFIX_APP + CommonConstant.JWT_HEADER_TOKEN_KEY), null);
            } catch (Exception e) {
            }
        } catch (Exception e) {
            result = Response.error(e.getMessage());
        }
        return result;
    }

    /**
     * <p> 刷新token。
     */
    @RequestMapping(method = RequestMethod.GET, value = acPrefix + "refreshToken")
    @ApiOperation(value = "刷新token")
    public Response refreshToken() {
        log.info("refreshToken.........");
        Response result = new Response(0, "success");
        try {
            String authorizationToken = request.getHeader(CommonConstant.JWT_HEADER_TOKEN_KEY);
            if (ValidatorUtil.isEmpty(authorizationToken)) {
                authorizationToken = request.getParameter(CommonConstant.JWT_HEADER_TOKEN_KEY);
            }
            if (ValidatorUtil.notEmpty(authorizationToken) && !authorizationToken.startsWith("Basic ")) {
                if (authorizationToken.startsWith(CommonConstant.JWT_HEADER_TOKEN_PREFIX_APP)) {//符合待处理token标记
                    authorizationToken = (String) cache.get(CommonConstant.JWT_CACHE_TOKEN_KEY_APP + authorizationToken);
                }
            }
            if (ValidatorUtil.isEmpty(authorizationToken)) {
                result = Response.error("token无效或过期,请重新登录!");
                return result;
            }
            UserDto staffDto = JSONObject.parseObject(JwtUtil.parseJWT(authorizationToken).getSubject(), UserDto.class);
            String xToken = CommonConstant.JWT_HEADER_TOKEN_PREFIX_APP + MD5.md5Hex(staffDto.getAccount() + IdUtil.createUUID(32) + IdUtil.createUUID(32));
            Map<String, Object> resultData = loginX(staffDto, xToken);
            result.data = resultData;
        } catch (Exception e) {
            result = Response.error(e.getMessage());
        }
        return result;
    }

    /**
     * <p> 获取ENCRYPT。
     */
    @RequestMapping(method = RequestMethod.POST, value = acPrefix + "encrypt")
    @ApiOperation(value = "获取ENCRYPT")
    public Response encrypt() {
        Response result = new Response(0, "success");
        try {
            result.data = new HashMap<String, String>() {{
                put("key", MD5.md5Hex(encryptConfig.getPRIKEY()));
            }};
        } catch (Exception e) {
            result = Response.error(e.getMessage());
        }
        return result;
    }

    /**
     * <p> 获取verifyCode。
     */
    @RequestMapping(method = RequestMethod.GET, value = acPrefix + "verify")
    @ApiOperation(value = "获取verifyCode")
    public void verify() throws IOException {
        response.reset(); // 清空输出流
        response.setContentType("application/octet-stream");
        String verifyCode = VerifyCodeUtil.outputVerifyImage(200, 80, response.getOutputStream(), 5);
        request.getSession().setAttribute(CommonConstant.LOGIN_VERIFY_CODE, verifyCode);
    }

    private Map<String, Object> loginX(UserDto staffDto, String xToken) throws Exception {
        Integer isSuper = 0;
        if (CommonConstant.TYPE_超管 == (staffDto.getType())) {
            //超级管理员标记
            staffDto.setIsSuper(1);
            request.getSession().setAttribute("isSuper", "1");
            isSuper = 1;
        }
        //角色集合
        Set<Long> roleIdSet = new HashSet<>();
        Set<String> rolesSet = new HashSet<>();
        //权限集合
        Set<String> permsSet = new HashSet<>();

        //2.获取角色集合
        List<AuthRoleDto> roleList = roleSourceService.getRoleListByUserId(staffDto);
        if (roleList != null) {
            for (AuthRoleDto role : roleList) {
                roleIdSet.add(role.getId());
                rolesSet.add(role.getName());
            }
        }
        staffDto.setRoleIds(roleIdSet);
        //3.获取功能集合
        List<AuthPermDto> permList = roleSourceService.getPermListByUserId(staffDto);
        if (permList != null) {
            for (AuthPermDto perm : permList) {
                if (perm.getMatchStr() != null && !"".equals(perm.getMatchStr())) {
                    permsSet.add(perm.getMatchStr());
                }
            }
        }

        staffDto.setAccountType(staffDto.getType());
//        staffDto.setXToken(xToken);
        staffDto.setIsSuper(isSuper);
        staffDto.setAuthorizationInfoRoles(rolesSet);
        staffDto.setAuthorizationInfoPerms(permsSet);

        String subject = JwtUtil.generalSubject(staffDto, STAFF_TOKEN);
        String authorizationToken = JwtUtil.createJWT(JwtUtil.UserType.STAFF, CommonConstant.JWT_ID, subject, CommonConstant.JWT_TTL);

        Map<String, Object> resultData = new HashMap<>();
//        Map<String, Object> appStaffata = new HashMap<>();
//        for (String field : STAFF_TOKEN) {
//            appStaffata.put(field, ReflectUtil.getValueByFieldName(staffDto, field));
//        }
        staffDto.setPassword(null);
        resultData.put("staff", staffDto);
        resultData.put("authorizationToken", xToken);
//        resultData.put("authorizationToken", authorizationToken);
        resultData.put("tokenExpMillis", System.currentTimeMillis() + CommonConstant.JWT_TTL_REFRESH);
        resultData.put("jwt_ttl", CommonConstant.JWT_TTL);
        cache.set(CommonConstant.JWT_CACHE_TOKEN_KEY_APP + xToken, authorizationToken);
        cache.set(CommonConstant.JWT_ISSUEAT + staffDto.getId(), JwtUtil.parseJWT(authorizationToken).getIssuedAt().getTime());

        // 河长在线统计
        if (staffDto != null && (CommonConstant.TYPES_各河长.indexOf(staffDto.getType()) != -1)) {
            OnLineCacheUtil.set(staffDto.getAccount(), 1, CommonConstant.ONLINE_CACHE_TIMEOUT);
        }
        return resultData;
    }
}
